/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef __ARKCORE_ACHIEVEMENTMGR_H
#define __ARKCORE_ACHIEVEMENTMGR_H

#include <map>
#include <string>

#include "Common.h"
#include <ace/Singleton.h>
#include "DatabaseEnv.h"
#include "DBCEnums.h"
#include "DBCStores.h"

typedef std::list<AchievementCriteriaEntry const*> AchievementCriteriaEntryList;
typedef std::list<AchievementEntry const*> AchievementEntryList;

typedef std::map<uint32, AchievementCriteriaEntryList> AchievementCriteriaListByAchievement;
typedef std::map<uint32, AchievementEntryList> AchievementListByReferencedId;

struct CriteriaProgress {
	uint32 counter;
	time_t date; // latest update time.
	bool changed;
};

enum AchievementCriteriaDataType { // value1         value2        comment
	ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE = 0, // 0              0
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE = 1, // creature_id    0
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2, // class_id       race_id
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH = 3, // health_percent 0
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD = 4, // own_team       0             not corpse (not released body), own_team == false if enemy team expected
	ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA = 5, // spell_id       effect_idx
	ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA = 6, // area id        0
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA = 7, // spell_id       effect_idx
	ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE = 8, // minvalue                     value provided with achievement update must be not less that limit
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL = 9, // minlevel                     minlevel of target
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER = 10, // gender                       0=male; 1=female
	ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT = 11, // scripted requirement
	ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY = 12, // difficulty                   normal/heroic difficulty for current event map
	ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT = 13, // count                        "with less than %u people in the zone"
	ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM = 14, // team                         HORDE(67), ALLIANCE(469)
	ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK = 15, // drunken_state  0             (enum DrunkenState) of player
	ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY = 16, // holiday_id     0             event in holiday time
	ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE = 17, // min_score      max_score     player's team win bg and opposition team have team score in range
	ACHIEVEMENT_CRITERIA_DATA_INSTANCE_SCRIPT = 18, // 0              0             maker instance script call for check current criteria requirements fit
	ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM = 19, // item_level     item_quality  for equipped item in slot to check item level and quality
	ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID = 20,
// map_id         0             player must be on map with id in map_id
};

#define MAX_ACHIEVEMENT_CRITERIA_DATA_TYPE               21 // maximum value in AchievementCriteriaDataType enum
class Player;
class Unit;

struct AchievementCriteriaData {
	AchievementCriteriaDataType dataType;
	union {
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE              = 0 (no data)
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE        = 1
		struct {
			uint32 id;
		} creature;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2
		struct {
			uint32 class_id;
			uint32 race_id;
		} classRace;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH = 3
		struct {
			uint32 percent;
		} health;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD     = 4
		struct {
			uint32 own_team_flag;
		} player_dead;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA            = 5
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA            = 7
		struct {
			uint32 spell_id;
			uint32 effect_idx;
		} aura;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA            = 6
		struct {
			uint32 id;
		} area;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE             = 8
		struct {
			uint32 minvalue;
		} value;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL           = 9
		struct {
			uint32 minlevel;
		} level;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER          = 10
		struct {
			uint32 gender;
		} gender;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT            = 11 (no data)
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY    = 12
		struct {
			uint32 difficulty;
		} difficulty;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT  = 13
		struct {
			uint32 maxcount;
		} map_players;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM            = 14
		struct {
			uint32 team;
		} team;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK           = 15
		struct {
			uint32 state;
		} drunk;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY           = 16
		struct {
			uint32 id;
		} holiday;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE= 17
		struct {
			uint32 min_score;
			uint32 max_score;
		} bg_loss_team_score;
		// ACHIEVEMENT_CRITERIA_DATA_INSTANCE_SCRIPT        = 18 (no data)
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM    = 19
		struct {
			uint32 item_level;
			uint32 item_quality;
		} equipped_item;
		// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID            = 20
		struct {
			uint32 mapId;
		} map_id;
		// ...
		struct {
			uint32 value1;
			uint32 value2;
		} raw;
	};
	uint32 ScriptId;

	AchievementCriteriaData() :
			dataType(ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE) {
		raw.value1 = 0;
		raw.value2 = 0;
		ScriptId = 0;
	}

	AchievementCriteriaData(uint32 _dataType, uint32 _value1, uint32 _value2,
			uint32 _scriptId) :
			dataType(AchievementCriteriaDataType(_dataType)) {
		raw.value1 = _value1;
		raw.value2 = _value2;
		ScriptId = _scriptId;
	}

	bool IsValid(AchievementCriteriaEntry const* criteria);
	bool Meets(uint32 criteria_id, Player const* source, Unit const* target,
			uint32 miscvalue1 = 0) const;
};

struct AchievementCriteriaDataSet {
	AchievementCriteriaDataSet() :
			criteria_id(0) {
	}
	typedef std::vector<AchievementCriteriaData> Storage;
	void Add(AchievementCriteriaData const& data) {
		storage.push_back(data);
	}
	bool Meets(Player const* source, Unit const* target,
			uint32 miscvalue = 0) const;
	void SetCriteriaId(uint32 id) {
		criteria_id = id;
	}
private:
	uint32 criteria_id;
	Storage storage;
};

typedef std::map<uint32, AchievementCriteriaDataSet> AchievementCriteriaDataMap;

struct AchievementReward {
	uint32 titleId[2];
	uint32 itemId;
	uint32 sender;
	std::string subject;
	std::string text;
};

typedef std::map<uint32, AchievementReward> AchievementRewards;

struct AchievementRewardLocale {
	StringVector subject;
	StringVector text;
};

typedef std::map<uint32, AchievementRewardLocale> AchievementRewardLocales;

struct CompletedAchievementData {
	time_t date;
	bool changed;
};

typedef UNORDERED_MAP<uint32, CriteriaProgress> CriteriaProgressMap;
typedef UNORDERED_MAP<uint32, CompletedAchievementData> CompletedAchievementMap;

class Unit;
class Player;
class WorldPacket;

class AchievementMgr {
public:
	AchievementMgr(Player* pl);
	~AchievementMgr();

	void Reset();
	static void DeleteFromDB(uint32 lowguid);
	void LoadFromDB(PreparedQueryResult achievementResult,
			PreparedQueryResult criteriaResult);
	void SaveToDB(SQLTransaction& trans);
	void ResetAchievementCriteria(AchievementCriteriaTypes type,
			uint64 miscvalue1 = 0, uint64 miscvalue2 = 0,
			bool evenIfCriteriaComplete = false);
	void UpdateAchievementCriteria(AchievementCriteriaTypes type,
			uint64 miscvalue1 = 0, uint64 miscvalue2 = 0, Unit *unit = NULL,
			uint32 time = 0);
	void CompletedAchievement(AchievementEntry const* entry,
			bool ignoreGMAllowAchievementConfig = false);
	void CheckAllAchievementCriteria();
	void SendAllAchievementData();
	void SendRespondInspectAchievements(Player* player);
	bool HasAchieved(AchievementEntry const* achievement) const;
	Player* GetPlayer() {
		return m_player;
	}
	void UpdateTimedAchievements(uint32 timeDiff);
	void StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry,
			uint32 timeLost = 0);
	void RemoveTimedAchievement(AchievementCriteriaTimedTypes type,
			uint32 entry); // used for quest and scripted timed achievements
	uint32 GetAchievementPoints() {
		return achievementPoints;
	}
private:
	enum ProgressType {
		PROGRESS_SET, PROGRESS_ACCUMULATE, PROGRESS_HIGHEST
	};
	void SendAchievementEarned(AchievementEntry const* achievement);
	void SendCriteriaUpdate(AchievementCriteriaEntry const* entry,
			CriteriaProgress const* progress, uint32 timeElapsed,
			bool timedCompleted);
	CriteriaProgress* GetCriteriaProgress(
			AchievementCriteriaEntry const* entry);
	void SetCriteriaProgress(AchievementCriteriaEntry const* entry,
			uint32 changeValue, ProgressType ptype = PROGRESS_SET);
	void RemoveCriteriaProgress(AchievementCriteriaEntry const* entry);
	void CompletedCriteriaFor(AchievementEntry const* achievement);
	bool IsCompletedCriteria(AchievementCriteriaEntry const* criteria,
			AchievementEntry const* achievement);
	bool IsCompletedAchievement(AchievementEntry const* entry);
	//void CompleteAchievementsWithRefs(AchievementEntry const* entry);

	void Compress(WorldPacket& data, uint32 newOpcode);
	void Compress(void* dst, uint32 *dst_size, void* src, int src_size);

	Player* m_player;
	CriteriaProgressMap m_criteriaProgress;
	CompletedAchievementMap m_completedAchievements;
	uint32 achievementPoints;
	typedef std::map<uint32, uint32> TimedAchievementMap;
	TimedAchievementMap m_timedAchievements; // Criteria id/time left in MS
};

class AchievementGlobalMgr {
	friend class ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex> ;
	AchievementGlobalMgr() {
	}
	~AchievementGlobalMgr() {
	}

public:
	AchievementCriteriaEntryList const& GetAchievementCriteriaByType(
			AchievementCriteriaTypes type);
	AchievementCriteriaEntryList const& GetTimedAchievementCriteriaByType(
			AchievementCriteriaTimedTypes type);
	AchievementCriteriaEntryList const* GetAchievementCriteriaByAchievement(
			uint32 id) {
		AchievementCriteriaListByAchievement::const_iterator itr =
				m_AchievementCriteriaListByAchievement.find(id);
		return itr != m_AchievementCriteriaListByAchievement.end() ?
				&itr->second : NULL;
	}

	AchievementEntryList const* GetAchievementByReferencedId(uint32 id) const {
		AchievementListByReferencedId::const_iterator itr =
				m_AchievementListByReferencedId.find(id);
		return itr != m_AchievementListByReferencedId.end() ?
				&itr->second : NULL;
	}

	AchievementReward const* GetAchievementReward(
			AchievementEntry const* achievement) const {
		AchievementRewards::const_iterator iter = m_achievementRewards.find(
				achievement->ID);
		return iter != m_achievementRewards.end() ? &iter->second : NULL;
	}

	AchievementRewardLocale const* GetAchievementRewardLocale(
			AchievementEntry const* achievement) const {
		AchievementRewardLocales::const_iterator iter =
				m_achievementRewardLocales.find(achievement->ID);
		return iter != m_achievementRewardLocales.end() ? &iter->second : NULL;
	}

	AchievementCriteriaDataSet const* GetCriteriaDataSet(
			AchievementCriteriaEntry const *achievementCriteria) {
		AchievementCriteriaDataMap::const_iterator iter =
				m_criteriaDataMap.find(achievementCriteria->ID);
		return iter != m_criteriaDataMap.end() ? &iter->second : NULL;
	}

	bool IsRealmCompleted(AchievementEntry const* achievement) const {
		return m_allCompletedAchievements.find(achievement->ID)
				!= m_allCompletedAchievements.end();
	}

	void SetRealmCompleted(AchievementEntry const* achievement) {
		m_allCompletedAchievements.insert(achievement->ID);
	}

	void LoadAchievementCriteriaList();
	void LoadAchievementCriteriaData();
	void LoadAchievementReferenceList();
	void LoadCompletedAchievements();
	void LoadRewards();
	void LoadRewardLocales();
private:
	AchievementCriteriaDataMap m_criteriaDataMap;

	// store achievement criterias by type to speed up lookup
	AchievementCriteriaEntryList m_AchievementCriteriasByType[ACHIEVEMENT_CRITERIA_TYPE_TOTAL];
	AchievementCriteriaEntryList m_AchievementCriteriasByTimedType[ACHIEVEMENT_TIMED_TYPE_MAX];
	// store achievement criterias by achievement to speed up lookup
	AchievementCriteriaListByAchievement m_AchievementCriteriaListByAchievement;
	// store achievements by referenced achievement id to speed up lookup
	AchievementListByReferencedId m_AchievementListByReferencedId;

	typedef std::set<uint32> AllCompletedAchievements;
	AllCompletedAchievements m_allCompletedAchievements;

	AchievementRewards m_achievementRewards;
	AchievementRewardLocales m_achievementRewardLocales;
};

#define sAchievementMgr ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex>::instance()

#endif
